package ua.edu.nuos.jeetraining2012.cms.util.shiro;

import io.buji.oauth.OAuthToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.JdbcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ua.edu.nuos.jeetraining2012.cms.dto.predefined.StandardUserStatuses;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Implementation of JdbcRealm for CMS Project and GlassFish.
 * This implementation used as secondary realm after OAuthRealm to check if user is locally blocked
 * and should not be able to authenticate
 */
public class CMSJdbcRealm extends JdbcRealm {

    private static final Logger log = LoggerFactory.getLogger(CMSJdbcRealm.class);

    protected String jndiDataSourceName;

    /**
     * Default constructor
     */
    public CMSJdbcRealm() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof OAuthToken;
    }

    /**
     * Getter for Jndi
     *
     * @return JNDI data source name
     */
    public String getJndiDataSourceName() {
        return jndiDataSourceName;
    }

    /**
     * Setter for jndi
     *
     * @param jndiDataSourceName JNDI datasource name
     */
    public void setJndiDataSourceName(String jndiDataSourceName) {
        this.jndiDataSourceName = jndiDataSourceName;
        this.dataSource = getDataSourceFromJNDI(jndiDataSourceName);
    }


    /**
     * Lookup DataSource from Application server
     *
     * @param jndiDataSourceName DataSource name
     * @return DataSource
     */
    protected DataSource getDataSourceFromJNDI(String jndiDataSourceName) {
        try {
            InitialContext ic = getInitialContext();
            return (DataSource) ic.lookup(jndiDataSourceName);
        } catch (NamingException e) {
            log.error("JNDI error while retrieving " + jndiDataSourceName, e);
            throw new AuthenticationException(e);
        }
    }

    /**
     * Create new InitialContext object
     *
     * @return instantiated InitialContext
     */
    protected InitialContext getInitialContext() throws NamingException {
        return new InitialContext();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        OAuthToken oAuthToken = (OAuthToken) token;
        String id = oAuthToken.getPrincipal().toString();
        log.debug("OAuthToken principal {} received", id);
        String status = getStatus(id);
        if ((status == null) || (!status.equals(StandardUserStatuses.BLOCKED))) {
            log.debug("OAuthToken id {} status not blocked", id);
            SimplePrincipalCollection collection = new SimplePrincipalCollection();
            collection.add(oAuthToken.getPrincipal(), getClass().toString());
            return new SimpleAuthenticationInfo(collection, oAuthToken.getCredentials());
        }
        log.debug("OAuthToken id {} status blocked", id);
        return null;
    }

    /**
     * Lookup user status in storage
     *
     * @param id user id to lookup
     * @return user status
     */
    protected String getStatus(String id) {
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            statement = conn.prepareStatement(authenticationQuery);
            statement.setString(1, id);

            resultSet = statement.executeQuery();

            boolean hasAccount = resultSet.next();
            if (!hasAccount)
                return null;
            return resultSet.getString(1);
        } catch (SQLException e) {
            final String message = "There was a SQL error while authenticating user [" + id + "]";
            log.error(message, e);
            throw new AuthenticationException(message, e);
        } finally {
            JdbcUtils.closeResultSet(resultSet);
            JdbcUtils.closeStatement(statement);
            JdbcUtils.closeConnection(conn);
        }
    }
}
